//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

use git2::{BranchType, Error, Repository};
use serde::Serialize;
use std::path::PathBuf;

#[derive(Serialize)]
struct HeadInfo {
    commit_id: String,
    branch_name: String,
    detached: bool,
}

#[derive(Serialize)]
struct BranchInfo {
    name: String,
    upstream: String,
    commit_id: String,
    timestamp: i64,
}

#[derive(Serialize)]
struct TagInfo {
    name: String,
    commit_id: String,
    timestamp: i64,
}

#[derive(Serialize)]
struct RemoteInfo {
    name: String,
    url: String,
}

#[derive(Serialize)]
struct GitInfo {
    head: HeadInfo,
    branch_list: Vec<BranchInfo>,
    tag_list: Vec<TagInfo>,
    remote_list: Vec<RemoteInfo>,
}

pub fn exec(git_path: PathBuf) -> Result<(), Error> {
    let repo = Repository::open(git_path);
    if repo.is_err() {
        return Err(repo.err().unwrap());
    }
    let repo = repo.unwrap();

    let mut git_info = GitInfo {
        head: HeadInfo {
            commit_id: "".into(),
            branch_name: "".into(),
            detached: false,
        },
        branch_list: Vec::new(),
        tag_list: Vec::new(),
        remote_list: Vec::new(),
    };

    //获取head信息
    let res = calc_head(&repo, &mut git_info);
    if res.is_err() {
        return res;
    }
    //获取分支信息
    let res = calc_branch(&repo, &mut git_info);
    if res.is_err() {
        return res;
    }
    //获取标记信息
    let res = calc_tag(&repo, &mut git_info);
    if res.is_err() {
        return res;
    }
    //获取远程服务器
    let res = calc_remote(&repo, &mut git_info);
    if res.is_err() {
        return res;
    }

    println!("{}", serde_json::to_string(&git_info).unwrap());

    Ok(())
}

fn calc_head(repo: &Repository, git_info: &mut GitInfo) -> Result<(), Error> {
    let head_ref = repo.head();
    if head_ref.is_err() {
        return Err(head_ref.err().unwrap());
    }
    let head_ref = head_ref.unwrap();
    let head_oid = head_ref.target();
    if head_oid.is_some() {
        git_info.head.commit_id = head_oid.unwrap().to_string();
    }
    let detached = repo.head_detached();
    if detached.is_err() {
        return Err(detached.err().unwrap());
    }
    git_info.head.detached = detached.unwrap();
    Ok(())
}

fn calc_branch(repo: &Repository, git_info: &mut GitInfo) -> Result<(), Error> {
    let branchs = repo.branches(Some(BranchType::Local));
    if branchs.is_err() {
        return Err(branchs.err().unwrap());
    }
    let branchs = branchs.unwrap();
    for branch in branchs {
        if branch.is_err() {
            continue;
        }

        let branch = branch.unwrap();
        let branch_oid = branch.0.get().target();
        let mut commit_id = String::from("");
        if branch_oid.is_some() {
            let branch_oid = branch_oid.unwrap();
            commit_id = branch_oid.to_string();
        }

        let mut branch_name = String::from("");
        let name = branch.0.name();
        if name.is_ok() {
            let name = name.unwrap();
            branch_name = name.unwrap_or_default().into();
        }
        if branch.0.is_head() {
            git_info.head.branch_name = branch_name.clone();
        }

        let mut upstream = String::from("");
        let up = branch.0.upstream();
        if up.is_ok() {
            let up = up.unwrap();
            let name = up.name();
            if name.is_ok() {
                let name = name.unwrap();
                upstream = name.unwrap_or_default().into();
            }
        }
        let mut timestamp = 0 as i64;
        let commit = branch.0.get().peel_to_commit();
        if commit.is_ok() {
            timestamp = commit.unwrap().time().seconds() * 1000;
        }

        git_info.branch_list.push(BranchInfo {
            name: branch_name,
            upstream: upstream,
            commit_id: commit_id,
            timestamp: timestamp,
        });
    }
    git_info
        .branch_list
        .sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
    Ok(())
}

fn calc_tag(repo: &Repository, git_info: &mut GitInfo) -> Result<(), Error> {
    let res = repo.tag_foreach(|oid, name| {
        let obj = repo.find_object(oid, None);
        if obj.is_err() {
            return true;
        }
        let obj = obj.unwrap();
        let commit = obj.peel_to_commit();
        if commit.is_err() {
            return true;
        }
        let commit = commit.unwrap();
        git_info.tag_list.push(TagInfo {
            name: String::from_utf8_lossy(name).to_string(),
            commit_id: commit.id().to_string(),
            timestamp: commit.time().seconds() * 1000,
        });
        return true;
    });
    if res.is_err() {
        return Err(res.err().unwrap());
    }
    git_info
        .tag_list
        .sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
    Ok(())
}

fn calc_remote(repo: &Repository, git_info: &mut GitInfo) -> Result<(), Error> {
    let remotes = repo.remotes();
    if remotes.is_err() {
        return Err(remotes.err().unwrap());
    }
    let remotes = remotes.unwrap();
    for remote in &remotes {
        if remote.is_none() {
            continue;
        }
        let remote = remote.unwrap();
        let remote_info = repo.find_remote(remote);
        if remote_info.is_err() {
            continue;
        }
        let remote_info = remote_info.unwrap();
        let mut remote_name = String::from("");
        let name = remote_info.name();
        if name.is_some() {
            remote_name = String::from(name.unwrap());
        }

        let mut remote_url = String::from("");
        let url = remote_info.url();
        if url.is_some() {
            remote_url = String::from(url.unwrap());
        }
        git_info.remote_list.push(RemoteInfo {
            name: remote_name,
            url: remote_url,
        });
    }
    Ok(())
}
